/*
 * RemoteIO.cpp
 *
 * Created: 29.10.2013 16:15:21
 *  Author: Elektronik
 */ 

#include "RemoteIO.h"
#include "digital_pins.h"
#include "pins_config.h"
#include "system_config.h"
#include "heater.h"
//=======================================================================
static void RemoteIO_UpdateIO();

//=======================================================================

#ifdef REMOTE_IO_SLAVE
	static void UpdateOutputs();
	static void UpdateInputs();
	static void OutputsToFailState();
#endif
//=======================================================================

// obraz vstupu/vystupu
RemoteIOOutputs outputs;
RemoteIOInputs inputs;

//========================================================================
// buffer pro odeslani zprav, zarovnano aby se nemuselo resit horni byte adresy
static uint8_t send_buffer[SEND_BUFFER_SIZE] __attribute__((aligned(16)));

// buffer pro prijem zprav, zarovnano aby se nemusel resit horni byte adresy
static uint8_t rec_buffer[REC_BUFFER_SIZE] __attribute__((aligned(16)));

// ukazatel na byte, ktery ma byt odeslan
volatile uint8_t send_cnt;

// pocet prijatych bytu
volatile uint8_t rec_cnt;

//=================================================================================
// Smycka zprav, u slave jednotky se musi volat manage heaters, master ji vola sam
void RemoteIO_Loop()
{
	#if defined REMOTE_IO_SLAVE
		manage_heaters();
	#endif
}

//=================================================================================
#ifdef REMOTE_IO_SLAVE

void UpdateOutputs()
{
	WritePin(OUT_0_PIN, outputs.out_0 != INVERT_OUTPUTS);
	WritePin(OUT_1_PIN, outputs.out_1 != INVERT_OUTPUTS);
	WritePin(OUT_2_PIN, outputs.out_2 != INVERT_OUTPUTS);
	WritePin(OUT_3_PIN, outputs.out_3 != INVERT_OUTPUTS);
	WritePin(OUT_4_PIN, outputs.out_4 != INVERT_OUTPUTS);
	WritePin(OUT_5_PIN, outputs.out_5 != INVERT_OUTPUTS);
	WritePin(OUT_6_PIN, outputs.out_6 != INVERT_OUTPUTS);
	WritePin(OUT_7_PIN, outputs.out_7 != INVERT_OUTPUTS);
	WritePin(OUT_8_PIN, outputs.out_8 != INVERT_OUTPUTS);
	WritePin(OUT_9_PIN, outputs.out_9 != INVERT_OUTPUTS);
	
	set_fan1_pwm(outputs.out_pwm0);
	set_fan2_pwm(outputs.out_pwm1);
	
	bed_heater.target_temp = outputs.out_temp0;
	extruder_heaters[EXT1].target_temp = outputs.out_temp1;
	extruder_heaters[EXT2].target_temp = outputs.out_temp2;
}

//==========================================================================
void UpdateInputs()
{
	inputs.in_0 = ReadPin(IN_0_PIN) != INVERT_INPUTS;
	inputs.in_1 = ReadPin(IN_1_PIN) != INVERT_INPUTS;
	inputs.in_2 = ReadPin(IN_2_PIN) != INVERT_INPUTS;
	inputs.in_3 = ReadPin(IN_3_PIN) != INVERT_INPUTS;
	inputs.in_4 = ReadPin(IN_4_PIN) != INVERT_INPUTS;
	inputs.in_5 = ReadPin(IN_5_PIN) != INVERT_INPUTS;
	inputs.in_6 = ReadPin(IN_6_PIN) != INVERT_INPUTS;
	inputs.in_7 = ReadPin(IN_7_PIN) != INVERT_INPUTS;
	//inputs.in_8 = ReadPin(IN_8_PIN); // sdileny s pinem 6
	inputs.in_9 = ReadPin(IN_9_PIN) != INVERT_INPUTS;
	
	inputs.in_temp0 = bed_heater.current_temp;
	inputs.in_temp1 = extruder_heaters[EXT1].current_temp;
	inputs.in_temp2 = extruder_heaters[EXT2].current_temp;
}
//==========================================================================
static void OutputsToFailState()
{
	outputs.out_0 = 0;
	outputs.out_1 = 0;
	outputs.out_2 = 0;
	outputs.out_3 = 0;
	outputs.out_4 = 0;
	outputs.out_5 = 0;
	outputs.out_6 = 0;
	outputs.out_7 = 0;
	outputs.out_8 = 0;
	outputs.out_9 = 0;
	
	outputs.out_pwm0 = 0;
	outputs.out_pwm1 = 0;
	
	outputs.out_temp0 = 0;
	outputs.out_temp1 = 0;
	outputs.out_temp2 = 0;
	
	UpdateOutputs();
	
	set_fan1_pwm(0);
	set_fan2_pwm(0);
	set_bed_heater_pwm(0);
	set_heater1_pwm(0);
	set_heater2_pwm(0);
}

#endif

//=======================================================================================================

volatile static uint8_t rec_msg_counter;
volatile static uint8_t comm_fail_counter;
volatile static uint8_t auto_send_counter;
//======================================================================================================

FORCE_INLINE uint8_t IsRemoteIOCommOk()
{
	return (comm_fail_counter == 0 ? 0 : 0xff);
}

//=======================================================================================================
// Obsluha preruseni odesilani, prepsano do assembleru, kde rutina potreboval 57 cyklu v nejlepsim pripade
// Optimalizace 36/41 cyklu

ISR(USART0_UDRE_vect, __attribute__((naked)))
{
	asm volatile (
	"push	r24							\n\t"
	"in		r24, __SREG__				\n\t"
	"push	r24							\n\t"
	"push	r30							\n\t"
	"push	r31							\n\t"
	//---------------------------------------
	"ldi	r30, lo8(%[buf])			\n\t"
	"ldi	r31, hi8(%B[buf])			\n\t"
	"lds	r24, %[index]				\n\t"
	"add	r30, r24					\n\t"	// zarovnano na 16bytu, neresim preteceni
	"ld		r30, z						\n\t"
	"sts	%[send_port], r30			\n\t"
	"inc	r24							\n\t"
	"sts	%[index], r24				\n\t"
	"ldi	r30, %[max_index]			\n\t"
	"cp		r24, r30					\n\t"
	"brne	end_%=						\n\t"
	"lds	r24, %[port_ctrl]			\n\t"
	"andi	r24, ~(1 << 5)				\n\t"	// zakaz dalsiho preruseni odesilani
	"ori	r24, (1 << 4)				\n\t"	// povoleni prijmu dat
	"sts	%[port_ctrl], r24			\n\t"
	//---------------------------------------
	"end_%=:							\n\t"
	"pop	r31							\n\t"
	"pop	r30							\n\t"
	"pop	r24							\n\t"
	"out	__SREG__, r24				\n\t"
	"pop	r24							\n\t"
	"reti								\n\t"
	:
	:	[buf] "i" (send_buffer),
	[index] "i" (&send_cnt),
	[max_index] "M" (SEND_BUFFER_SIZE),
	[send_port] "i" (_SFR_MEM_ADDR(UDR0)),
	[port_ctrl] "i" (_SFR_MEM_ADDR(UCSR0B))
	: );
}

//========================================================================================================
// Prijem na rozhrani USART0

ISR(USART0_RX_vect, OPTIMIZE_O3)
{
	// uloz prijaty byte
	rec_buffer[rec_cnt] = UDR0;
	
	// pokud je tento byte prvni spustim hlidani 
	if (rec_cnt == 0)
		rec_msg_counter = REC_MSG_TIMEOUT;
		
	// presun citac na dalsi volne misto
	++rec_msg_counter;
	
	// nacten pozadovany pocet bytu
	if (rec_msg_counter == REC_BUFFER_SIZE)
	{
		// ignoruj dalsi byty na vstupu
		UCSR0B &= ~(1 << RXEN0);
	
		//kontrola, zda jsou data platna
		uint16_t checkum = calc_checkum16(rec_buffer, REC_BUFFER_SIZE);
			
		// data jsou platna
		if (checkum == 0)
		{
			// zresetuji casovac chyby spojeni
			comm_fail_counter = COM_FAIL_TIMEOUT;
			
			// master jednota zkopiruje obraz vstupu
			#ifdef REMOTE_IO_MASTER
				memcpy(&inputs, &rec_buffer[1], sizeof(inputs));
			// slave jednotka zkopiruje obraz vystupu a katualizuje vystupy,
			// zkopiruje vstupy do obrazu a odesle zpravu masteru
			#elif defined REMOTE_IO_SLAVE
				memcpy(&outputs, &rec_buffer[1], sizeof(outputs));
				UpdateOutputs();
				UpdateInputs();
				RemoteIO_UpdateIO();
			#endif
		}
	}
}

//========================================================================
// 1ms preruseni pro kontrolu timeoutu zpravy a komunikace

ISR(TIMER5_COMPA_vect, OPTIMIZE_SIZE)
{
	// kontrola, zda byla cela zprava prijata v casovem intervalu
	// pokud ne, tak se prijata data zahodi
	if (rec_msg_counter != 0)
	{
		// vyprsel cas primu zpravy, povol dalsi prijem
		if (--rec_msg_counter == 0) 
		{
			rec_cnt = 0;
		}
	}
	
	// kontrola, zda v casovem intervalu dochazeji zpravy 
	if (comm_fail_counter != 0)
	{
		if (--comm_fail_counter == 0)
		{
			// u slave jednotky se pri preruseni spojeni nastavi vystupy do chyboveho stavu
			#ifdef REMOTE_IO_SLAVE
				OutputsToFailState();
			#endif
		}
	}
	
	// po vyprseni casovace odesli zpravu do vzdalene jednotky
	#ifdef REMOTE_IO_MASTER
		if (--auto_send_counter == 0)
		{
			--auto_send_counter = RESEND_TIME;
			RemoteIO_UpdateIO();
		}
	#endif
}

//========================================================================
// Inicializace vzdalenych vstupu/vystupu
OPTIMIZE_O3 void RemoteIO_Begin(uint8_t brd)
{
	// povoleni prijmu zprav
	rec_cnt = 0;
	
	// // vypnuti hlaseni o nedoruceni cele zpravy
	rec_msg_counter = 0;					
	
	// aktivace hlidani preruseni komunikace
	comm_fail_counter = COM_FAIL_TIMEOUT;	
	
	// master spusti automaticke odesilani zprav
	#ifdef REMOTE_IO_MASTER					
		auto_send_counter = RESEND_TIME;
	#endif
	
	// prednstaveni vystupu do chyboveho stavu
	#ifdef REMOTE_IO_SLAVE
		OutputsToFailState();				
	#endif
	
	// incicializace serioveho portu 0
	UBRR0L = brd;
	UBRR0H = 0;
	UCSR0A = (1 << U2X0);
	UCSR0B = (1 << RXCIE0) | (1 << RXEN0) | (1 << TXEN0);
	UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
	
	// inicializace hlidaciho casovace
	TCCR5A = 0;
	TCCR5B = (1 << CS51) | (1 << CS50) | (1 << WGM52);	// 1:64, CTC Mode OCR5A is TOP
	TCCR5C = 0;
	OCR5A = 250;					// COMP5A interrupt every 1ms
	TIMSK5 = (1 << OCIE5A);			// enable interrupt for COMP5A and COMP5B
	
	RemoteIO_UpdateIO();
}

//========================================================================

OPTIMIZE_O3 void RemoteIO_UpdateIO()
{
	// do prvniho bytu ulozim cislo o 1 vetsi, nez jsem naposledy prijal
	// to umozni detekci kratkodobeho preruseni spojeni, zatim nevyuzito
	send_buffer[0] = rec_buffer[0] + 1;							
	
	// prekopirovani obrazu do odesilajiciho bufferu
	#ifdef REMOTE_IO_MASTER										
		memcpy(&send_buffer[1], &outputs, sizeof(outputs));
	#elif defined REMOTE_IO_SLAVE
		memcpy(&send_buffer[1], &inputs, sizeof(inputs));
	#else
		#error "No remote type specified at func RemoteIO_Update"
	#endif
	
	// vypocet kontrolniho souctu odesilanych dat, slouzi ke kontrole platnosti dat
	uint16_t checkum = calc_checkum16(send_buffer, SEND_BUFFER_SIZE - 2);
	uint16_t * ptr = (uint16_t*)&send_buffer[SEND_BUFFER_SIZE-2];
	*ptr = checkum;
	
	// spusteni dalsiho prenosu
	send_cnt = 0;
	rec_cnt = 0;
	UCSR0B |= (1 << UDRIE0);
}

//=========================================================================